package com.gitee.chili.leisure.util;

import com.gitee.chili.leisure.enums.CardType;
import com.gitee.chili.leisure.exception.BusinessException;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;

/**
 * MJUtil
 *
 * @author week solider
 * @since 2020.09.21
 */
public class AdaptionAlgorithm {

    public static void main(String[] args) {
        // 七对
//        List<Integer> list = new ArrayList<>(Arrays.asList(1, 1, 8, 8, 11, 11, 22, 22, 9, 9, 6, 6, 7));
        // 清一色
//        List<Integer> list = new ArrayList<>(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7));
        // 胡牌最多的牌型
//        List<Integer> list = new ArrayList<>(Arrays.asList(1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9));
        // 碰碰胡
//        List<Integer> list = new ArrayList<>(Arrays.asList(2, 2, 2, 8, 8, 11, 11, 23, 23, 23, 26, 26, 26));
        // 将对
        List<Integer> list = new ArrayList<>(Arrays.asList(5, 5, 5, 22, 22, 25, 25));
        // 台面上的(杠、碰)牌
        List<Integer> spe = new ArrayList<>(Arrays.asList(2, 2, 2, 2, 18, 18, 18, 18));
        Collections.sort(list);
        long start = System.currentTimeMillis();
        List<Integer> readyHand = checkedReadyHand(list);
        long end = System.currentTimeMillis();

        // 统计番数
        System.out.println("耗时：" + (end - start) + "ms");
        System.out.println(list);
        System.out.println("可以胡的牌：" + readyHand);
//        list.add(0);
//        System.out.println(isSame(list));
        System.out.println(calculationPoints(list, spe, readyHand));
    }


    /**
     * 检查听牌信息 (不适用于七对, 在末尾中单独对七对做了特殊校验)
     * <p>
     * 删减法, 检查过程:
     * 使用所有的牌型来进行检查 -> 获取到对子牌型 -> 移除对子(当对子牌型多个时，也先移除一个) -> 移除刻子 -> 移除顺子 -> 结果手中的牌被清空
     * 则循环到的这张牌是这副牌的听牌
     * <p>
     * 胡牌的规则为：有一个同样的两张牌做将，然后剩下的组成ABC或者AAA的形式。
     *
     * @param handCards    手牌
     * @return 所有听的牌
     */
    public static List<Integer> checkedReadyHand(List<Integer> handCards) {

        int size = handCards.size();

        checkCardsLength(size);

        // 手牌只剩一张
        if (size == 1)
            return Collections.singletonList(handCards.get(0));

        List<Integer> readyHands = new ArrayList<>();
        // 1~30 是所有的牌型 (不包括  10、20)
        for (int i = 1; i < 30; i++) {
            if (i == 10 || i == 20)
                continue;

            List<Integer> newHandCards = new ArrayList<>(handCards);
            // 添加到手牌进行匹配
            newHandCards.add(i);

            // 查找所有对子牌
            List<Integer> pairs = searchPair(newHandCards);
            if (pairs.isEmpty())
                continue;

            for (Integer pair : pairs) {
                ArrayList<Integer> temp = new ArrayList<>(newHandCards);
                Collections.sort(temp);
                // 移除对子
                clearPair(temp, pair);
                // 移除刻子
                clearTriplet(temp);
                // 移除顺子
                clearSequence(temp);
                // 移除后手牌被清空代表可以胡
                if (temp.isEmpty()) {
                    readyHands.add(i);
                    break;
                }
            }
        }

        // 没有碰和杠才需要检查是否是七对
        if (size == 13) {
            // 判断是否是七对
            Integer sevenPair = checkSevenPair(handCards);
            if (sevenPair > 0) {
                if (!readyHands.contains(sevenPair)) {
                    readyHands.add(sevenPair);
                }
            }
        }

        return readyHands;
    }

    /**
     * 检查是否可以碰
     *
     * @param handCards 手牌
     * @param pongCard  碰牌
     */
    public static boolean checkPong(List<Integer> handCards, Integer pongCard) {
        return checkSpecificCard(handCards, pongCard, 1);
    }

    /**
     * 检查自杠
     */
    public static List<Integer> checkKong(List<Integer> handCards) {
        Map<Integer, Integer> map = new HashMap<>();
        for (Integer next : handCards) {
            map.merge(next, 1, Integer::sum);
        }

        List<Integer> kong = new ArrayList<>();

        map.keySet().forEach(k -> {
            if (map.get(k) > 3) {
                kong.add(k);
            }
        });

        return kong;
    }

    /**
     * 检查碰、杠 (一定是别人打出的牌)
     *
     * @param handCards 手牌
     * @param card      需要检查的牌
     * @param type      1:碰 、2:杠
     */
    public static boolean checkSpecificCard(List<Integer> handCards, Integer card, int type) {
        int size = type == 1 ? 2 : 3;

        Map<Integer, Integer> map = new HashMap<>();
        for (Integer next : handCards) {
            map.merge(next, 1, Integer::sum);
        }

        if (map.containsKey(card)) {
            return map.get(card) == size;
        }

        return false;
    }

    /**
     * 检查点杠
     *
     * @param handCards 手牌
     * @param card      需要检查的牌
     */
    public static boolean checkKong(List<Integer> handCards, Integer card) {
        return checkSpecificCard(handCards, card, 2);
    }

    /**
     * 计算玩家番数
     *
     * @param handCards    玩家手牌
     * @param specialCards 台面上的牌 (碰，杠)
     * @param readyHands   玩家的听牌
     */
    public static Map<Integer, Integer> calculationPoints(List<Integer> handCards, List<Integer> specialCards, List<Integer> readyHands) {
        // 记录番数  (K: 牌, V: 番)
        Map<Integer, Integer> pointMaps = new HashMap<>();

        // 当没有碰和杠的时候才需要进行添加七队数据
        if (handCards.size() == 13) {
            // 添加七对数据
            populateSevenPair(pointMaps, handCards, readyHands);

            // 添加门清数据
            // 门清: 没有杠、碰等操作就算门清
            populateDoorClear(pointMaps, handCards, readyHands);
        }

        // 添加碰碰胡数据
        populatePongPong(pointMaps, handCards, readyHands);

        // 整合台面上的牌 (一定要在添加七队数据后)
        if (Objects.nonNull(specialCards) && !specialCards.isEmpty()) {
            handCards.addAll(specialCards);
        }

        // 添加清一色数据
        populateSame(pointMaps, handCards, readyHands);

        // 添加中张数据
        // 中张: 没有1(筒条万)和9(筒条万)
        populateMiddleSheet(pointMaps, handCards, readyHands);

        // 添加将对数据
        // 将对: 手中所有牌都是二五八
        populateEyesPair(pointMaps, handCards, readyHands);

        // 添加根数据(四川麻将中的杠叫根)
        populateKong(pointMaps, handCards, readyHands);


        return pointMaps;
    }

    /**
     * 是否是中张
     *
     * @param handCards 手牌
     */
    private static boolean isMiddleSheet(List<Integer> handCards) {
        AtomicBoolean res = new AtomicBoolean(true);
        handCards.forEach(hc -> {
            if (hc == 1 || hc == 9 || hc == 11 || hc == 19 || hc == 21 || hc == 29) {
                res.set(false);
            }
        });
        return true;
    }

    /**
     * 是否是将对
     * <p>
     * 手中所有牌都是二五八
     *
     * @param handCards 手牌(这里包含了台面上显示的牌，也就是杠、碰)
     */
    private static boolean isEyesPair(List<Integer> handCards) {
        for (Integer card : handCards) {
            if (card != 2 && card != 5 && card != 8 && card != 12 && card != 15
                    && card != 18 && card != 22 && card != 25 && card != 28) {
                return false;
            }
        }
        return true;
    }

    /**
     * 是否碰碰胡
     */
    private static List<Integer> checkPongPong(List<Integer> handCards) {
        // 统计牌数
        Map<Integer, Integer> map = new HashMap<>();
        handCards.forEach(hc -> map.merge(hc, 1, Integer::sum));

        // 所有胡牌
        List<Integer> hands = new ArrayList<>();

        LongAdder singleCount = new LongAdder();
        LongAdder pairCount = new LongAdder();
        LongAdder colCount = new LongAdder();

        map.forEach((k, v) -> {
            if (v == 1) {
                singleCount.increment();
                hands.add(k);
            } else if (v == 2) {
                pairCount.increment();
                hands.add(k);
            } else if (v == 3) {
                colCount.increment();
            } else if (v == 4) {
                //手上有4个一样的牌，在四川麻将中是和不了对对胡的 随便加点东西
                singleCount.increment();
                pairCount.add(2);
            }
        });

        if ((pairCount.sum() == 2 && singleCount.sum() == 0)
                || (pairCount.sum() == 0 && singleCount.sum() == 1)) {
            return hands;
        }

        return null;
    }

    /**
     * 是否是清一色
     *
     * @param handCards 手牌
     */
    private static boolean isSame(List<Integer> handCards) {
        // 获取到第一张的类型
        AtomicReference<CardType> type = new AtomicReference<>(typeOf(handCards.get(0)));
        return handCards.stream().allMatch(hc -> type.get().equals(typeOf(hc)));
    }

    /**
     * 获取张牌的类型
     * 万: 1-9
     * 条: 11-19
     * 筒: 21-29
     * 风: 暂时没有
     *
     * @return 1:表示万、2:表示条、3:表示筒
     */
    public static CardType typeOf(int card) {
        if (card > 0 && card < 10) {
            return CardType.character;
        } else if (card > 10 && card < 20) {
            return CardType.bamboo;
        } else if (card > 20 && card < 30) {
            return CardType.dot;
        } else {
            throw new BusinessException("未知牌型");
        }
    }

    /**
     * 是否是七对
     *
     * @param handCards 手牌
     */
    private static Integer checkSevenPair(List<Integer> handCards) {
        Map<Integer, Integer> map = new HashMap<>();
        handCards.forEach(c -> map.merge(c, 1, Integer::sum));

        AtomicInteger card = new AtomicInteger();
        LongAdder count = new LongAdder();

        map.forEach((k, v) -> {
            if (v == 2 || v == 3) {
                count.increment();
            } else if (v == 4) {
                count.add(2);
            }

            if (v == 1 || v == 3) {
                // 如果已经有单牌了，表示不止一张单牌，并没有下叫。直接闪
                if (card.get() > 0) {
                    return;
                }
                card.set(k);
            }
        });

        // 有6对才表示是七对
        if (count.sum() == 6) {
            return card.get();
        }

        return -1;
    }

    /**
     * 检查手牌长度是否合规
     *
     * @param length 长度
     */
    private static void checkCardsLength(int length) {
        if (length != 1 && length != 4 && length != 7 && length != 10 && length != 13) {
            throw new BusinessException("牌的张数造成了违规");
        }
    }

    /**
     * 找到手牌中所有的对子
     *
     * @param handCards 手牌
     */
    private static List<Integer> searchPair(List<Integer> handCards) {
        List<Integer> pairs = new ArrayList<>();
        for (int i = 0; i < handCards.size(); i++) {
            for (int j = i + 1; j < handCards.size(); j++) {
                Integer value = handCards.get(i);
                if (value.equals(handCards.get(j))) {
                    if (!pairs.contains(value)) {
                        pairs.add(value);
                    }
                }
            }
        }
        return pairs;
    }

    /**
     * 清除对子
     *
     * @param newHandCards 手牌
     * @param pairCard     对子牌
     */
    private static void clearPair(List<Integer> newHandCards, int pairCard) {
        int index = 0;
        Iterator<Integer> iterator = newHandCards.iterator();
        while (iterator.hasNext()) {
            Integer nextCard = iterator.next();
            if (nextCard.equals(pairCard)) {
                iterator.remove();
                index++;
            }
            if (index > 1) {
                break;
            }
        }
    }

    /**
     * 移除刻子
     *
     * @param newHandCards 手牌
     */
    private static void clearTriplet(List<Integer> newHandCards) {
        Map<Integer, Integer> map = new HashMap<>();
        for (Integer next : newHandCards) {
            map.merge(next, 1, Integer::sum);
        }

        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue() > 2) {
                int index = 0;
                Iterator<Integer> iterator = newHandCards.iterator();
                while (iterator.hasNext()) {
                    Integer next = iterator.next();
                    if (entry.getKey().equals(next)) {
                        iterator.remove();
                        index++;
                    }
                    if (index > 2) {
                        break;
                    }
                }
            }
        }
    }

    /**
     * 移除顺子
     */
    private static void clearSequence(List<Integer> handCards) {
        for (int i = 0; i < handCards.size(); i++) {
            for (int j = i + 1; j < handCards.size(); j++) {
                for (int k = j + 1; k < handCards.size(); k++) {
                    // 是否是一个顺子
                    if (isSequence(handCards, i, j, k)) {
                        handCards.remove(i);
                        handCards.remove(j - 1);
                        handCards.remove(k - 2);
                        clearSequence(handCards);
                    }
                }
            }
        }
    }

    /**
     * 是否是一个顺子
     *
     * @param cards 需要验证的牌
     * @param i     位置 1
     * @param j     位置 2
     * @param k     位置 3
     */
    private static boolean isSequence(List<Integer> cards, int i, int j, int k) {
        return cards.get(i) + 1 == cards.get(j) && cards.get(j) + 1 == cards.get(k);
    }

    /**
     * 添加碰碰胡数据
     *
     * @param pointMaps  番数集
     * @param handCards  手牌
     * @param readyHands 听牌集
     */
    private static void populatePongPong(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHands) {
        List<Integer> ppVal = checkPongPong(handCards);
        if (Objects.nonNull(ppVal) && !ppVal.isEmpty()) {
            // 碰碰胡 1番
            ppVal.forEach(pp -> {
                if (readyHands.contains(pp)) {
                    merge(pointMaps, pp, 1);
                }
            });
        }
    }

    /**
     * 添加七对数据
     *
     * @param pointMaps  番数集
     * @param handCards  手牌
     * @param readyHands 听牌集
     */
    private static void populateSevenPair(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHands) {
        Integer sevenPair = checkSevenPair(handCards);
        if (sevenPair > 0 && readyHands.contains(sevenPair)) {
            // 七对基础番 2番
            merge(pointMaps, sevenPair, 2);
        }
    }

    /**
     * 添加清一色数据
     *
     * @param pointMaps  番数集
     * @param handCards  手牌
     * @param readyHands 听牌集
     */
    private static void populateSame(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHands) {
        List<Integer> newHandCards = new ArrayList<>(handCards);

        for (Integer hand : readyHands) {
            newHandCards.add(hand);
            // 是否是清一色
            if (isSame(newHandCards)) {
                // 清一色基础番 2番
                merge(pointMaps, hand, 2);
                continue;
            }
            newHandCards.remove(hand);

            // 普通牌型0番
            merge(pointMaps, hand, 0);
        }
    }

    /**
     * 填充中张数据
     *
     * @param pointMaps 番数集
     * @param handCards 手牌
     */
    private static void populateMiddleSheet(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHand) {
        List<Integer> newHandCards = new ArrayList<>(handCards);
        readyHand.forEach(rh -> {
            newHandCards.add(rh);
            if (isMiddleSheet(newHandCards)) {
                // 中张牌型 每张牌都加 1番
                merge(pointMaps, rh, 1);
            }
            newHandCards.remove(rh);
        });
    }

    /**
     * 填充门清数据
     *
     * @param pointMaps 番数集
     * @param handCards 手牌
     */
    private static void populateDoorClear(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHands) {
        if (handCards.size() == 13) {
            readyHands.forEach(rh -> {
                // 门清 每张牌都加 1番
                merge(pointMaps, rh, 1);
            });
        }
    }

    /**
     * 填充将对数据
     *
     * @param pointMaps 番数集
     * @param handCards 手牌
     */
    private static void populateEyesPair(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHands) {
        List<Integer> newHandCards = new ArrayList<>(handCards);
        readyHands.forEach(rh -> {
            newHandCards.add(rh);
            if (isEyesPair(newHandCards)) {
                // 将对牌型 每张牌都加 4番
                merge(pointMaps, rh, 4);
            }
            newHandCards.remove(rh);
        });
    }

    /**
     * 填充根数据
     *
     * @param pointMaps 番数集
     * @param handCards 手牌
     */
    private static void populateKong(Map<Integer, Integer> pointMaps, List<Integer> handCards, List<Integer> readyHands) {
        List<Integer> kong = checkKong(handCards);
        readyHands.forEach(rh -> merge(pointMaps, rh, kong.size()));
    }

    /**
     * 统计番数
     *
     * @param map   番数集
     * @param card  胡牌
     * @param point 番数
     */
    private static void merge(Map<Integer, Integer> map, Integer card, Integer point) {
        map.merge(card, point, Integer::sum);
    }

}
